<?php
/**
 * MineAdmin is committed to providing solutions for quickly building web applications
 * Please view the LICENSE file that was distributed with this source code,
 * For the full copyright and license information.
 * Thank you very much for using MineAdmin.
 *
 * @Author X.Mo<root@imoi.cn>
 * @Link   https://gitee.com/xmo/MineAdmin
 */

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */

namespace App\Aspect;

use App\Annotation\OperationLog;
use App\Annotation\Permission;
use App\Event\Operation;
use App\Service\AdminMenuService;
use App\Service\AdminUserService;
use Exception;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\HttpServer\Contract\RequestInterface;
use Ip2region;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Class OperationLogAspect.
 */
#[Aspect]
class OperationLogAspect extends AbstractAspect
{
    public array $annotations = [
        OperationLog::class,
    ];

    protected Ip2region $ip2region;

    protected AdminUserService $adminUserService;

    public function __construct()
    {
        $this->ip2region = container()->get(Ip2region::class);
    }

    /**
     * @return mixed|void
     * @throws \Hyperf\Di\Exception\Exception
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        $annotation = $proceedingJoinPoint->getAnnotationMetadata()->method[OperationLog::class];
        /* @var $result ResponseInterface */
        $result = $proceedingJoinPoint->process();
        $isDownload = false;

        $logName = $annotation->menuName ?? $annotation->logName;

        if ($logName || ($annotation = ($proceedingJoinPoint->getAnnotationMetadata()->method[Permission::class] ?? null))) {
            if (!empty($result->getHeader('content-description')) && !empty($result->getHeader('content-transfer-encoding'))) {
                $isDownload = true;
            }
            event(new Operation($this->getRequestInfo([
                'code' => !empty($annotation->code) ? explode(',', $annotation->code)[0] : '',
                'name' => $logName ?? '',
                'response_code' => $result->getStatusCode(),
                'response_data' => $isDownload ? '文件下载' : $result->getBody()->getContents(),
            ])));
        }
        return $result;
    }

    /**
     * 获取客户端ip地址
     * @return mixed
     */
    public function ip(RequestInterface $request)
    {
        $res = $request->getServerParams();
        if (isset($res['http_client_ip'])) {
            return $res['http_client_ip'];
        }
        if (isset($res['http_x_real_ip'])) {
            return $res['http_x_real_ip'];
        }
        if (isset($res['http_x_forwarded_for'])) {
            // 部分CDN会获取多层代理IP，所以转成数组取第一个值
            $arr = explode(',', $res['http_x_forwarded_for']);
            return $arr[0];
        }
        return $res['remote_addr'];
    }

    protected function getIpLocation($ip)
    {
        $info = $this->ip2region->btreeSearch($ip);

        [$country, $number, $province, $city, $network] = explode('|', $info['region']);
        if ($country == '中国') {
            return $province . '-' . $city . ':' . $network;
        }
        if ($country == '0') {
            return '未知';
        }
        return $country;
    }

    /**
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    protected function getRequestInfo(array $data): array
    {
        $request = container()->get(RequestInterface::class);

        $serverParams = $request->getServerParams();
        $ip = $this->ip($request);
        $operationLog = [
            'time' => date('Y-m-d H:i:s', $serverParams['request_time']),
            'method' => $serverParams['request_method'],
            'router' => $serverParams['path_info'],
            'protocol' => $serverParams['server_protocol'],
            'ip' => $ip,
            'ip_location' => $this->getIpLocation($ip),
            'log_name' => $data['name'] ?: $this->getOperationMenuName($data['code']),
            'request_data' => $request->all(),
            'response_code' => $data['response_code'],
            'response_data' => $data['response_data'],
        ];
        try {
            if ($userId = userID()) {
                $user = container()->get(AdminUserService::class)->getCacheInfo((int)$userId);
                $operationLog['user_id'] = $userId;
                $operationLog['username'] = $user['user']['username'] ?? '';
            }
        } catch (Exception $e) {
            $operationLog['username'] = '系统';
        }

        return $operationLog;
    }

    /**
     * 获取菜单名称.
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    protected function getOperationMenuName(string $code): string
    {
        return container()->get(AdminMenuService::class)->findNameByCode($code);
    }
}
